import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class BoardImplFast implements Board 
{
	
	
	protected CellType[][] board;
	protected boolean[][] deadlock;
	
//	private ArrayList<Cord> boxes;
//	private ArrayList<Cord> goals;
	protected short[] boxes;
	protected Cord[] goals;
	protected Cord player;
	
	protected byte[] hashArray;
	
	private int hashCode;
	
//	private HashMap<Integer,VisitedEntry> visitedMap;
	
//	protected boolean boxMoved;
	
	public void init( CellType[][] cellBoard )
	{
		board = new Board.CellType[cellBoard.length][cellBoard[0].length];
		
		for ( int x = 0; x < cellBoard.length; x++ )
		{
			for ( int y = 0; y < cellBoard[0].length; y++ )
			{
//				System.out.println( "x="+y +"\tcellBoard[0].length="+cellBoard[0].length );
//				System.out.println( "y="+y +"\tcellBoard.length="+cellBoard.length );
				board[x][y] = cellBoard[x][y];
			}
		}
		
//		this.board = board;
//		this.boxes = boxes;
//		this.goals = goals;
//		this.player = player;
		
//		boxMoved = false;
		
		ArrayList<CordImpl> boxesArr = new ArrayList<CordImpl>();
		ArrayList<Cord> goalsArr = new ArrayList<Cord>();
		
        for ( int x = 0; x < board.length; x++ )
        {
        	for ( int y = 0; y < board[0].length; y++ )
        	{
        		if ( board[x][y] == CellType.Player || board[x][y] == CellType.PlayerOnGoalSquare )
        		{
        			player =   new CordImpl( x, y );
        		}
        		
        		if ( board[x][y] == CellType.GoalSquare || 
        				board[x][y] == CellType.PlayerOnGoalSquare ||
        				board[x][y] == CellType.BoxOnGoal)
        		{
        			goalsArr.add( new CordImpl( x, y ) );
        		}
        		
        		if ( board[x][y] == CellType.Box || board[x][y] == CellType.BoxOnGoal )
        		{
        			boxesArr.add( new CordImpl( x, y ) );
        		}
        	}
        }
        
        /*
         * Nollstaall braadet och ta bort spelare och boxar
         */
        setCellType( player , CellType.EmptyFloor);
        
        for ( Cord box : boxesArr )
        {
        	setCellType( box , CellType.EmptyFloor);
        }
        
        for ( Cord goal : goalsArr )
        {
        	setCellType( goal , CellType.GoalSquare);
        }
        /*
         * 
         */
        
//		boxes = new Cord[boxesArr.size()];
//		for ( int i = 0; i < boxes.length; i++)
//		{
//			boxes[i] = boxesArr.get(i);
//		}
		
		boxes = new short[boxesArr.size()];
		for ( int i = 0; i < boxes.length; i++)
		{
			boxes[i] = boxesArr.get(i).getCords();
		}
		
		/*
		 * Sortera alla boxar
		 */
		Arrays.sort( boxes );
		
		goals = new Cord[goalsArr.size()];
		for ( int i = 0; i < goals.length; i++)
		{
			goals[i] = goalsArr.get(i);
		}
		
        
		
		hashArray = new byte[2+(boxes.length*2)];
//        this.boxes = (Cord[]) boxes.toArray();
//        this.goals = (Cord[]) goals.toArray();
        
//		  deadlock = new boolean[ board.length ][ board[0].length ];
		  
		  Deadlocks deadlocksCalc = new Deadlocks();
		  
			char[][] charBoard = new char[ board.length ][ board[0].length ];
			
			for ( int x = 0; x < board.length; x++ )
	        {
//				board.getCellType( x, y )
				for ( int y = 0; y < board[0].length; y++ )
				{
					charBoard[x][y] = getCellType(x,y).toChar();
//					System.out.print(board[x][y]);
		        }
//				System.out.println();
			}
			
			deadlock = deadlocksCalc.getDeadlocks( charBoard );
			
			// TODO tunnlar // testfall 35
			
//			deadlocksCalc.printBoolocks();
		  
//		  deadlock = CreateDeadLockArray.create( this );
		  
//		  deadlock = AndraDeadlockArray.create( this );
		
//        System.out.println("fff");
			
			updateHashCode();
			
//			visitedMap = new HashMap<Integer,VisitedEntry>();
	}
	
	public BoardImplFast( final CellType[][] board, final boolean[][] deadlock, byte[] hashArray, 
			final short[] boxes, final Cord[] goals, Cord player, final int hashCode )
	{
		this.board = board;
		this.deadlock = deadlock;
		
		this.boxes = boxes;
		this.goals = goals;
		this.player = player;	
		
		this.hashArray = hashArray;
		
		this.hashCode = hashCode;
		
//		this.visitedMap = visitedMap;
		
//		boxMoved = false;
	}
	
	public BoardImplFast( CellType[][] cellBoard )
	{
		

	}

	@Override
	public List<Move> getLegalMoves(Cord currentPosition, MoveRule moveRule)
	{
		ArrayList<Move> newMoves = new ArrayList<Move>(4);
		
//		for ( Direction direction : Direction.getValues() )
		for ( Move.Direction direction : Move.Direction.values() )
		{
//			Move newPotentiallyLegalMove = getAdjacentMoveInDirection( currentPosition, direction );
			Move newPotentiallyLegalMove = new MoveImpl( currentPosition.doNewMove(direction), direction );
			
			if (    ( isPotentiallyLegalMove(newPotentiallyLegalMove) ) && 
					( moveRule.isLegalMove(this, newPotentiallyLegalMove, direction) ) )
//			else if ( moveRule.isLegalMove( this , currentPosition, direction) )
			{
//				newMoves.add( getAdjacentMoveInDirection( currentPosition, direction ) );
				newMoves.add(newPotentiallyLegalMove);
			}
		}
		
		return newMoves;
	}
	
	/**
	 * Kollar om ett drag aer potentiellt giltigt, ignorerar drag som hamnar utanfaor braeder t.ex. eller
	 * saadana som aer in i vaeggar.
	 */
	private boolean isPotentiallyLegalMove( Move move )
	{
//		boolean exists = true;
		
//		exists &= existsCellAt(move);
		
		if ( !existsCellAt(move) )
		{
			return false;
		}
		
//		exists &= (getCellType(move) != CellType.Wall);
		
		if ( (getCellType(move) == CellType.Wall) )
		{
			return false;
		}
		
		return true;

//		exists &= ( board[x][y] == Board.EmptyFloor ) || ( board[x][y] == Board.GoalSquare );
		
//		return exists;
	}
	
	public boolean[][] getDeadlockArray()
	{
		return deadlock;
	}
	
	public int getXLength()
	{
		return board.length;
	}
	
	public int getYLength()
	{
		return board[0].length;
	}

	@Override
	public CellType getCellType(Cord cord) 
	{
//		if ( ! existsCellAt( cord ) )
//		{
//			throw new IllegalArgumentException("Koordinaterna faor givet move utanfaor braedet.");
//		}
		
//		return( board[ cord.getX() ][ cord.getY() ] );
		return getCellType( cord.getX(), cord.getY() ) ;
	}
	
	protected CellType getCellType( short coords ) 
	{
		return getCellType( CordImpl.getXFromCords(coords), CordImpl.getYFromCords(coords)); 
	}

	protected void setCellType( Cord cord, CellType type )
	{
//		System.out.println( "innan: "+cord+ ", "+board[ cord.getX() ][ cord.getY() ] );
		board[ cord.getX() ][ cord.getY() ] = type;
//		System.out.println( "efter: "+cord+ ", "+board[ cord.getX() ][ cord.getY() ] );
	}
	
	protected void setCellType( int x, int y, CellType type )
	{
//		System.out.println( "innan: "+cord+ ", "+board[ cord.getX() ][ cord.getY() ] );
		board[ x ][y ] = type;
//		System.out.println( "efter: "+cord+ ", "+board[ cord.getX() ][ cord.getY() ] );
	}
	
//	public int boxesHashCode()
//	{
//		return boxesHashCode;
//	}
//	
//	public void setVisitedStateCache( VisitedEntry visitedEntry )
//	{
//		visitedEntryCache = visitedEntry;
//	}
//	
//	public VisitedEntry getVisitedEntryCache()
//	{
//		return visitedEntryCache;
//	}
	
	public void updateHashCode()
	{
		hashArray[0] = (byte) player.getX();
		hashArray[1] = (byte) player.getY();
		
		
		int counter = 0;
		for ( short box : boxes )
		{
			counter += 2;
			
			hashArray[ counter ] = (byte) CordImpl.getXFromCords(box);
			hashArray[ counter+1 ] = (byte) CordImpl.getYFromCords(box);
			
		}
		
//		int hash = 9;
//		int multi = 13;
//		
//		for ( byte b : hashArray )
//		{
//			hash *= multi*b;
//		}
//		
//		hashCode = hash;

		hashCode = Arrays.hashCode(hashArray);
	}
	
	@Override
	public int hashCode()
	{
//		BoardImplFast fastBoard = board;
		
//		Arrays.sort( boxes );
		
//		byte[] hashArray = new byte[2+(boxes.length*2)];
		
//		hashArray[0] = (byte) player.getX();
//		hashArray[1] = (byte) player.getY();
//		
//		
//		int counter = 0;
//		for ( short box : boxes )
//		{
//			counter += 2;
//			
//			hashArray[ counter ] = (byte) CordImpl.getXFromCords(box);
//			hashArray[ counter+1 ] = (byte) CordImpl.getYFromCords(box);
//			
//		}
		
//		return Arrays.hashCode(hashArray);
		
		return hashCode;
	}
	
	@Override
	public CellType getCellType(int x, int y) 
	{
//		if ( ! existsCellAt( x, y ) )
//		{
//			throw new IllegalArgumentException("Koordinaterna faor givet move utanfaor braedet.");
//		}
		
		CellType cellType = ( board[ x ][ y] );
		
		short coords = CordImpl.getCoordsFromXY(x, y);
		
		/*
		 * If player
		 */
		if ( ( x==player.getX() ) &&  ( y==player.getY() ) )
		{
	        switch ( cellType ) 
	        {
		        case EmptyFloor: 
		        	
		        	return CellType.Player;
		        	
		        case GoalSquare: 
		        	return CellType.PlayerOnGoalSquare;
		        	
		        default: throw new IllegalArgumentException( "bugg! " + cellType );
	        }
		}
//		else if ( Arrays.binarySearch(boxes, coords) >= 0 )
		else if ( hasBoxAt(coords) )
		{
	        switch ( cellType ) 
	        {
		        case EmptyFloor: 
		        	
		        	return CellType.Box;
		        	
		        case GoalSquare: 
		        	return CellType.BoxOnGoal;
		        	
		        default: throw new IllegalArgumentException( "bugg! " + cellType );
	        }
		}
		else
		{
			return cellType;
		}
//		
//		return( board[ x ][ y] );
	}
	
	protected void changeBoxCoordinates( short currentCoords, short newCoords )
	{
		short[] newBoxes = new short[boxes.length];
		for ( int i = 0; i < boxes.length; i++)
		{
			newBoxes[i] = boxes[i];
		}
		
		newBoxes[ Arrays.binarySearch(newBoxes, currentCoords) ] = newCoords;
		Arrays.sort(newBoxes);
		
		boxes = newBoxes;
		
//		boxes[ Arrays.binarySearch(boxes, currentCoords) ] = newCoords;
//		Arrays.sort(boxes);
		
//		boxMoved = true;
		
//		for ( int i = 0; i < boxes.length; i++)
//		{
//			if ( boxes[i] == currentCoords )
//			{ 
//				boxes[i] = newCoords;
//			}
//		}

	}
	
	protected boolean hasBoxAt( short coords )
	{
		return (Arrays.binarySearch(boxes, coords) >= 0);
//		for ( short box : boxes )
//		{
//			if ( box == coords )
//			{ 
//				return true;
//			}
//		}
//		return false;
	}
	
//	protected boolean hasBoxAt( int x, int y )
//	{
//		return hasBoxAt(CordImpl.getCoordsFromXY(x, y));
//	}

	@Override
	public VisitedCellHelper getVisitedBoardHelper() 
	{
		return new VisitedCellHelperImpl( board );
	}
	
//	public short[] getBoxesCoords()
//	{
//		return boxes;
//	}

	@Override
	public Cord[] getBoxes() 
	{
		// TODO optimera och aeteranvand minne
//		throw new NotYetImplementedException();
		Cord[] array  = new Cord[ boxes.length ];
		
		for ( int i = 0; i < array.length; i++ )
		{
			array[i] = new CordImpl( boxes[i] );
		}
		
		return array;
	}

	@Override
	public Cord[] getGoals() 
	{
//		throw new NotYetImplementedException();
		return goals;
	}

	@Override
	public Cord getPlayer() 
	{
		return player;
	}

	@Override
	public Board doMovePlayer(Move.Direction direction) 
	{

		CordImpl nextCord = new CordImpl( player.getX()+direction.getXDiff(), player.getY() + direction.getYDiff() );
		
		if ( getCellType( nextCord ).isBox() )
		{

			CordImpl nextNextCord = new CordImpl( nextCord.getX()+direction.getXDiff(), nextCord.getY() + direction.getYDiff() );
			
			changeBoxCoordinates( nextCord.getCords(), nextNextCord.getCords() );
			
			if ( getCellType( nextNextCord ).isWall() )
			{
				throw new RuntimeException("Bug i Olles kod, nextNextCord i wall");
			}
			
		}
		else if ( getCellType( nextCord ).isWall() )
		{
			throw new RuntimeException("Bug i Olles kod, nextCord i wall");
		}
		

		this.player = new CordImpl( nextCord.getX(), nextCord.getY() );
		
		updateHashCode();

		return null;
	}

	@Override
	public void printBoard() 
	{
        for ( int y = 0; y < board[0].length; y++ )
        {
        	for ( int x = 0; x < board.length; x++ )
        	{
//        		System.out.print( board[ x ][ y ] );
        		System.out.print( getCellType(x, y) );
        	}
        	System.out.print( "\n" );
        }
        
//        for ( int y = 0; y < board[0].length; y++ )
//        {
//        	for ( int x = 0; x < board.length; x++ )
//        	{
////        		System.out.print( board[ x ][ y ] );
//        		CellType type = getCellType(x, y);
//        		
//        		if ( type.isWall() )
//        		{
//        			System.out.print( type );
//        		}
//        		else
//        		{
//        			if ( deadlock[x][y] )
//        			{
//        				System.out.print( "D" );
//        			}
//        			else
//        			{
//        				System.out.print( CellType.EmptyFloor );
//        			}
//        		}
//        		
////        		System.out.print( getCellType(x, y) );
//        	}
//        	System.out.print( "\n" );
//        }
	}

	@Override
	public boolean existsCellAt( Cord cord )
	{
		return existsCellAt( cord.getX(), cord.getY() );
	}
	
	@Override
	public boolean existsCellAt( int x, int y )
	{
		boolean exists = true;
		
		exists &= 0 <= x;
		exists &= x < board.length;
		
		exists &= 0 <= y;
		exists &= y < board[0].length;
		
		return exists;
	}
	
	public BoardImplFast cloneThis()
	{
		return new BoardImplFast( board, deadlock, hashArray ,boxes, goals, 
				player.cloneCord(), hashCode );
	}
	
	@Override
	public Board clone()
	{

		
		//return new BoardImpl( newBoard,new ArrayList<Cord>(boxes),
		//new ArrayList<Cord>(goals), 
		// new CordImpl( player.getX(), player.getY() ) );
		
//		if ( boxMoved )
//		{
//			short[] newBoxes = new short[boxes.length];
//			for ( int i = 0; i < boxes.length; i++)
//			{
//				newBoxes[i] = boxes[i];
//			}
//			
//			return new BoardImplFast( board, deadlock, hashArray ,newBoxes, goals, player.cloneCord() );
//		}
//		else
//		{
		return new BoardImplFast( board, deadlock, hashArray ,boxes, goals, 
				player.cloneCord(), hashCode );
//		}
		
		

		
		//Cord[] newGoals = new Cord[goals.length];
		//for ( int i = 0; i < goals.length; i++)
		//{
		//	newGoals[i] = goals[i].cloneCord();
		//}
		
		//return new BoardImpl( newBoard,newBoxes,newGoals, player.cloneCord() );
//		return new BoardImplFast( board, deadlock, hashArray ,newBoxes, goals, player.cloneCord() );
		
	}
	
	@Override
	public boolean isFinished() 
	{
		for( short box : boxes )
		{
			if ( getCellType(box) != CellType.BoxOnGoal )
			{
				return false;
			}
		}
		// else
		return true;
	}

//	@Override
//	public boolean hasVisited() 
//	{
//		return visitedMap.containsKey( hashCode() );
//	}
//
//	@Override
//	public VisitedEntry getCurrentVisitedEntry() 
//	{
//		return visitedMap.get( hashCode() );
////		return visitedEntryCache;
//	}
//
//	@Override
//	public void visit(VisitedEntry entry) 
//	{
//		visitedMap.put( hashCode(), entry);
//	}
}
